<template>
  <div class="node-header" style="height: 40px">
    <div
      class="node-header-title"
      style="font-size: 18px; font-weight: 700; border-bottom: 1px solid #ccc"
    >
      节点关系图
      <!-- <el-button @click="router.go(-1)">返回到上一页</el-button> -->
    </div>
  </div>
  <div
    id="graph"
    v-loading="loading"
    ref="svgContainer"
    class="graph-container"
  ></div>
  <el-drawer v-model="drawer" :show-close="false">
    <template #header>
      <header style="font-size: 20px; font-weight: bold">
        {{ showDetail.name ? showDetail.name : '' }}
      </header>
    </template>
    <template v-if="JSON.stringify(showDetail) !== '{}'">
      <div class="desc" style="font-size: 14px; color: #aaa">
        <span style="font-size: 14px; margin-right: 10px">描述:</span>
        {{ showDetail.description ? showDetail.description : '无' }}
      </div>
    </template>
    <el-empty v-else></el-empty>
  </el-drawer>
</template>
<script setup lang="ts">
import { ref, shallowRef, onMounted, onUnmounted, watch } from 'vue'
import * as d3 from 'd3'
import { useRouter, useRoute } from 'vue-router'
import { httpNodeRelations } from '@/api/study'
import iconDo from '@/assets/icon-do.png'
import iconRe from '@/assets/icon-relationship.png'
import iconDe from '@/assets/icon-description.png'
const loading = ref(false)
const router = useRouter()
const route = useRoute()
const width = shallowRef()
const height = shallowRef()
const parentNode = ref()
const svgContainer = shallowRef(null)
const drawer = ref(false)
const showDetail = ref<any>({})
const nodes = ref([])
const links = ref([])
let relationshipText = null
// const svg = shallowRef(null)
const simulation = shallowRef(null)
const selectedNode = ref<any>({})

function removeRelationshipText() {
  if (relationshipText) {
    relationshipText.remove() // 移除文案
  }
}

// 判断节点是否与另一个节点连接
function isConnected(node1, node2) {
  return links.value.some(
    (link) =>
      (link.source === node1 && link.target === node2) ||
      (link.source === node2 && link.target === node1),
  )
}

// 拖动事件
function dragStarted(event, d) {
  if (!event.active) simulation.value.alphaTarget(0.3).restart()
  d.fx = d.x
  d.fy = d.y
  // 禁用其他节点的力作用
  // nodes.value.forEach(node => {
  //   if (node !== d && !isConnected(d, node)) {
  //     node.fx = node.x
  //     node.fy = node.y
  //   } else {
  //     node.fx = null
  //     node.fy = null
  //   }
  // })
}

function dragged(event, d) {
  d.fx = event.x
  d.fy = event.y
  //   nodes.value.forEach((node) => {
  //     if (node === d) {
  //       d.fx = event.x
  //       d.fy = event.y
  //     }
  //     //  else {
  //     //   node.fx = node.x
  //     //   node.fy = node.y
  //     // }
  //   })
}

function dragEnded(event, d) {
  if (!event.active) simulation.value.alphaTarget(0)
  d.fx = null
  d.fy = null
}

function showRelationshipText(linkData, g) {
  if (relationshipText) {
    relationshipText.remove() // 移除之前的关系文本
  }

  relationshipText = g
    .append('text')
    .attr('class', 'relationship-text')
    .attr('x', (linkData.source.x + linkData.target.x) / 2)
    .attr('y', (linkData.source.y + linkData.target.y) / 2)
    .attr('dy', -10) // 文本上移一点
    .text(linkData.relationship)
}

const renderChart = () => {
  // 创建SVG容器
  const svg = d3
    .select('#graph')
    .append('svg')
    .attr('width', width.value)
    .attr('height', height.value)
  const color = d3.scaleOrdinal(d3.schemeCategory10)
  const g = svg.append('g')
  // 创建力学模拟器
  simulation.value = d3
    .forceSimulation(nodes.value)
    .force(
      'link',
      d3
        .forceLink(links.value)
        .id((d) => d.id)
        .distance(50),
    )
    .force(
      'charge',
      d3
        .forceManyBody()
        .distanceMin(10)
        .distanceMax(200)
        .strength(1)
        .theta(0.4),
    )
    .force('center', d3.forceCenter(width.value / 2, height.value / 2))
    .force('collision', d3.forceCollide().radius(50).strength(1))
  // .force('x', d3.forceX(width.value * 2).strength(0.05))
  // .force('y', d3.forceY(height.value * 2).strength(0.05))
  for (let i = 0; i < 50; i++) {
    simulation.value.tick()
  }
  loading.value = false
  // 定义箭头
  g.append('defs')
    .append('marker')
    .attr('id', 'arrowhead')
    .attr('viewBox', '0 -5 10 10')
    .attr('refX', 26) // 设置箭头的偏移量
    .attr('refY', 0)
    .attr('orient', 'auto')
    .attr('markerWidth', 6)
    .attr('markerHeight', 6)
    .append('path')
    .attr('d', 'M0,-5L10,0L0,5')
    .attr('fill', '#aaa')
  // 绘制连线
  const link = g
    .append('g')
    .selectAll('.link')
    .data(links.value)
    // .enter()
    // .append('line')
    .join('line')
    .attr('class', 'link')
    .style('stroke', '#aaa')
    .attr('marker-end', 'url(#arrowhead)')
    .style('stroke-width', 2)
    .on('mouseover', function (event, d) {
      event.stopPropagation()
      // d3.select(this)
      //   .style('stroke-width', 5)
      //   .style('stroke', 'rgba(135, 206, 235, 0.3)')
      //   .style('stroke-padding', '5px')
      // .style('filter', 'drop-shadow(0 0 4px rgba(75, 0, 130, 0.5))')
      showRelationshipText(d, g)
    })
    .on('mouseout', function (event, d) {
      d3.select(this)
        .style('stroke-width', 2)
        .style('stroke-padding', '0px')
        .style('stroke', '#aaa')
        .style('filter', 'none')
      removeRelationshipText()
    })

  // 绘制节点
  const node = g
    .append('g')
    .selectAll('.node')
    .data(nodes.value)
    .join('g')
    .attr('class', 'node')
    .attr('fill', (d) => color(d.group))
    .on('mouseover', function (event, d) {
      // 添加外边环
      d3.select(this)
        .style('background-color', 'rgba(255, 255, 255, 0.3)')
        .style('cursor', 'pointer')
        .append('circle')
        .attr('class', 'hover-ring')
        .attr('r', 25) // 比内圆大一些
        .attr('fill', 'none')
        .attr('stroke', d3.select(this).attr('fill'))
        .attr('stroke-width', 3)
        .style('opacity', 0.5)
    })
    .on('mouseout', function (event, d) {
      // 移除外边环
      d3.select(this).select('.hover-ring').remove()
    })
    .call(
      d3
        .drag()
        .on('start', dragStarted)
        .on('drag', dragged)
        .on('end', dragEnded),
    )

  // 绘制小圆（内层圆）
  node.append('circle').attr('class', 'small-circle').attr('r', 20)

  // 为节点添加文案
  node
    .append('text')
    .attr('class', 'node-text')
    .attr('text-anchor', 'middle') // 水平居中
    .attr('y', 40) // 文案位置，放在节点下方
    .text((d) => d.name) // 显示节点的名称

  // 创建大圆环和扇形
  let currentBigCircle = null // 当前显示的大圆环
  let currentArcGroup = null // 当前显示的扇形

  // 更新力学模拟器
  simulation.value.on('tick', function () {
    link
      .attr('x1', (d) => d.source?.x)
      .attr('y1', (d) => d.source?.y)
      .attr('x2', (d) => d.target?.x)
      .attr('y2', (d) => d.target?.y)

    node.attr('transform', (d) => `translate(${d.x}, ${d.y})`)

    // 更新圆环和扇形的位置
    if (currentBigCircle) {
      currentBigCircle
        .attr('cx', currentBigCircle.data()[0].x)
        .attr('cy', currentBigCircle.data()[0].y)

      currentArcGroup.attr(
        'transform',
        `translate(${currentBigCircle.data()[0].x}, ${
          currentBigCircle.data()[0].y
        })`,
      )
    }
  })

  // 点击节点时，显示或隐藏大圆环和扇形
  node.on('click', function (event, d) {
    selectedNode.value = d
    event.stopPropagation() // 防止触发 svg 区域的点击事件
    // 如果当前已有显示的大圆环，且是同一节点，则移除它
    if (currentBigCircle && currentBigCircle.data()[0] === d) {
      currentBigCircle.remove()
      currentArcGroup.remove()
      currentBigCircle = null
      currentArcGroup = null
    } else {
      // 移除之前显示的圆环和扇形
      if (currentBigCircle) {
        currentBigCircle.remove()
        currentArcGroup.remove()
      }

      // 创建新的大圆环
      const radius = 60
      currentBigCircle = g
        .append('circle')
        .attr('class', 'big-circle')
        .attr('r', radius)
        .attr('cx', d.x)
        .attr('cy', d.y)
        .attr('fill', 'transparent')
        .data([d]) // 将节点数据绑定到圆环

      // 创建扇形区域（带间隔）
      const angleStep = (Math.PI * 2) / 3 // 每个扇形占120度
      const arc = d3
        .arc()
        .innerRadius(radius - 32) // 内半径
        .outerRadius(radius) // 外半径
        .startAngle((d, i) => i * angleStep + 0.05) // 扇形起始角度（增加间隔）
        .endAngle((d, i) => (i + 1) * angleStep - 0.05) // 扇形结束角度（增加间隔）
      // 创建扇形组
      currentArcGroup = g
        .append('g')
        .attr('class', 'arc-group')
        .attr('transform', `translate(${d.x}, ${d.y})`)

      // 创建扇形
      currentArcGroup
        .selectAll('.arc')
        .data([0, 1, 2])
        // .enter()
        // .append('path')
        .join('path')
        // .attr('class', 'arc')
        .attr('class', (x, i) => `arc arc-${i}`)
        .attr('d', arc)
        .attr('fill', (x, i) => d3.schemeCategory10[i])
        .style('opacity', 1)

      // 添加icon
      currentArcGroup
        .selectAll('.arc-icon')
        .data([0, 1, 2])
        // .enter()
        // .append('image')
        .join('image')
        .attr('class', (d) => `arc-icon arc-icon-${d}`)
        .attr('xlink:href', (d, i) => {
          return i === 0 ? iconDe : i === 1 ? iconRe : iconDo
        })
        .attr('width', 20)
        .attr('height', 20)
        .attr('transform', (d, i) => {
          // 计算扇形中心点位置
          const angleStep = (2 * Math.PI) / 3 // 每个扇形120度
          const radius = 50 // 扇形的半径
          const angle = i * angleStep + angleStep / 2 // 取扇形中心角度
          const centerRadius = (radius + (radius - 20)) / 2 // 取内外半径的中点
          const x = Math.cos(angle) * centerRadius // 计算x坐标
          const y = Math.sin(angle) * centerRadius // 计算y坐标
          if (i === 0) {
            return `translate(${-x + 5},${0}) translate(-10,0)` // 平移到扇形中心并偏移使图标居中
          } else if (i === 1) {
            return `translate(${2},${y / 2 - 8}) translate(10,-10)` // 平移到扇形中心并偏移使图标居中
          } else {
            return `translate(${x + 4},${-y}) translate(-10,-10)` // 平移到扇形中心并偏移使图标居中
          }
        })
        .attr('x', (d, i) => {
          const angle = i * angleStep + angleStep / 2
          // 计算扇形区域中心点，使用radius - 20作为半径(与arc的innerRadius对应)
          const centerRadius = (radius + (radius - 20)) / 2 // 取内外半径的中点
          return Math.cos(angle) * centerRadius - 10 // 减去icon宽度的一半以居中
        })
        .attr('y', (d, i) => {
          const angle = i * angleStep + angleStep / 2
          const centerRadius = (radius + (radius - 20)) / 2 // 取内外半径的中点
          return Math.sin(angle) * centerRadius - 10 // 减去icon高度的一半以居中
        })
        .style('transition', 'transform 0.3s ease')
        .on('click', function (event, d) {
          // event.stopPropagation() // 阻止事件冒泡
          //   console.log('d', d)
          if (d === 2) {
            // 刷题
            // console.log(selectedNode.value, '节点信息')
            const url = router.resolve({
              path: '/study/exam',
              query: {
                keyword_id: selectedNode.value.id,
                keyword_name: encodeURIComponent(selectedNode.value.name),
                course_id: route.query.course_id,
                pid: route.query.pid,
              },
            })
            window.open(url.href, '_blank')
          } else if (d === 0) {
            // 查看信息
            drawer.value = true
            showDetail.value = selectedNode.value
          } else if (d === 1) {
            // 查看关系
            if (selectedNode.value.id == parentNode.value.node_id) {
              return
            }
            if (selectedNode.value.id == route.query.node_id) {
              return
            }
            router.push({
              path: '/study/node',
              query: {
                node_id: selectedNode.value.id,
                course_id: parentNode.value.course_id,
                pid: route.query.pid,
              },
            })
          }
        })

      // 为扇形添加点击事件
      currentArcGroup.selectAll('.arc').on('click', function (event, d) {
        if (d === 0) {
          //  刷题
          const url = router.resolve({
            path: '/study/exam',
            query: {
              keyword_id: selectedNode.value.id,
              keyword_name: encodeURIComponent(selectedNode.value.name),
              course_id: route.query.course_id,
              pid: route.query.pid,
            },
          })
          window.open(url.href, '_blank')
        } else if (d === 1) {
          //  查看信息
          drawer.value = true
          showDetail.value = selectedNode.value
        } else if (d === 2) {
          // 查看关系
          if (selectedNode.value.id == route.query.node_id) {
            return
          }
          router.push({
            path: '/study/node',
            query: {
              node_id: selectedNode.value.id,
              course_id: parentNode.value.course_id,
              pid: route.query.pid,
            },
          })
        }
        // event.stopPropagation() // 阻止事件冒泡
      })
    }
  })
  // 添加缩放功能
  const zoom = d3
    .zoom()
    .scaleExtent([0.1, 4]) // 设置缩放范围
    .on('zoom', (event) => {
      g.attr('transform', event.transform)
    })

  // 将缩放行为应用到SVG容器
  svg.call(zoom).on('dblclick.zoom', null) // 禁用双击缩放
  // 点击非节点区域时隐藏所有大圆环
  g.on('click', function () {
    if (currentBigCircle) {
      currentBigCircle.remove()
      currentArcGroup.remove()
      currentBigCircle = null
      currentArcGroup = null
      selectedNode.value = null
    }
  })
}
const computedWidthHeight = () => {
  const rectDOM = svgContainer.value.getBoundingClientRect()
  width.value = rectDOM.width.toFixed(0)
  height.value = rectDOM.height.toFixed(0)
}

const getChartData = async () => {
  loading.value = true
  if (!parentNode.value.course_id || !parentNode.value.node_id) return
  const res = await httpNodeRelations({
    course_id: parentNode.value.course_id,
    node_id: parentNode.value.node_id,
  })
  if (res) {
    links.value = res.edges
    nodes.value = res.nodes.map((item) => {
      return {
        ...item,
        group: item.label,
      }
    })
  }
}
watch(
  () => route.query,
  async (newVal) => {
    parentNode.value = route.query
    await getChartData()
    renderChart()
  },
  {
    immediate: true,
    deep: true,
  },
)
onMounted(async () => {
  computedWidthHeight()
  window.addEventListener('resize', computedWidthHeight)
})
onUnmounted(() => {
  window.removeEventListener('resize', computedWidthHeight)
  if (simulation.value) {
    simulation.value.stop()
  }
})
</script>
<style lang="scss">
.graph-container {
  position: relative;
  height: calc(100% - 60px);
  min-height: 600px;
  width: 100%;
  overflow: hidden;
}

.node {
  cursor: pointer;
}

.big-circle {
  fill: none;
  /* stroke: steelblue; */
  stroke-width: 0;
}

.small-circle {
  stroke: white;
  stroke-width: 2;
}

.arc {
  stroke: none;
  opacity: 0.7;
  transition: opacity 0.3s ease, fill 0.3s ease;
  cursor: pointer;
  position: relative;

  &:hover {
    fill: orange !important;
  }
}

.arc-text {
  font-size: 12px;
  /* text-anchor: middle; */
  alignment-baseline: middle;
  cursor: pointer;
}

.link {
  stroke: #aaa;
  stroke-width: 2;
}

.node-text {
  font-size: 12px;
  fill: #000;
  text-anchor: middle;
}

.relationship-text {
  font-size: 16px;
  fill: black;
  font-weight: bold;
  pointer-events: none; /* 防止文本框影响点击事件 */
}

.arc-label {
  font-size: 14px;
  fill: #000;
  /* pointer-events: none; */
  position: absolute;
}

.arc-icon {
  position: absolute;
  z-index: 999;
  cursor: pointer;
}

.arc-group:has(.arc-icon-0:hover) > .arc-1 {
  fill: orange !important;
}

.arc-group:has(.arc-icon-1:hover) > .arc-2 {
  fill: orange !important;
}

.arc-group:has(.arc-icon-2:hover) > .arc-0 {
  fill: orange !important;
}
</style>
